<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <p data-lake-id="ub56e3421" id="ub56e3421"><span data-lake-id="u36bad2cf" id="u36bad2cf">作为一个Java后端开发，我们写出的大部分代码都决定着用户的使用体验。如果我们的后端代码性能不好，那么用户在访问我们的网站时就要浪费一些时间等待服务器的响应。这就可能导致用户投诉甚至用户的流失。</span></p>
  <p data-lake-id="u9c94ee79" id="u9c94ee79"><br></p>
  <p data-lake-id="u535e0bc4" id="u535e0bc4"><span data-lake-id="ufdfe7b12" id="ufdfe7b12">关于性能优化是一个很大的话题。《Java程序性能优化》说性能优化包含五个层次：设计调优、代码调优、JVM调优、数据库调优、操作系统调优等。而每一个层次又包含很多方法论和最佳实践。本文不想大而广的概述这些内容。只是举几个常用的Java代码优化方案，读者看完之后可以真正的实践到自己代码中的方案。</span></p>
  <p data-lake-id="uf126f992" id="uf126f992"><br></p>
  <h3 data-lake-id="b1bc894a" id="b1bc894a"><span data-lake-id="u90596064" id="u90596064">使用单例</span></h3>
  <p data-lake-id="u848d7662" id="u848d7662"><br></p>
  <p data-lake-id="ubb2024a4" id="ubb2024a4"><span data-lake-id="u8b9b4928" id="u8b9b4928">对于IO处理、数据库连接、配置文件解析加载等一些非常耗费系统资源的操作,我们必须对这些实例的创建进行限制,或者是始终使用一个公用的实例,以节约系统开销,这种情况下就需要用到单例模式。</span></p>
  <p data-lake-id="ua715779b" id="ua715779b"><br></p>
  <h3 data-lake-id="nruIk" id="nruIk"><span data-lake-id="u036613c3" id="u036613c3">批量操作</span></h3>
  <p data-lake-id="u63e9f536" id="u63e9f536"><br></p>
  <p data-lake-id="u055144b1" id="u055144b1"><span data-lake-id="ue0c1e29e" id="ue0c1e29e">有100个请求，每个请求单独执行那肯定很慢，如果有办法把这个100个请求合并成一个请求，进行批量操作，那么效率就会高很多。</span></p>
  <p data-lake-id="u4db16d01" id="u4db16d01"><span data-lake-id="ubb3d19ac" id="ubb3d19ac">​</span><br></p>
  <p data-lake-id="ucfe68c38" id="ucfe68c38"><span data-lake-id="u5af71cd0" id="u5af71cd0">尤其是在数据库操作的时候，批量操作不仅比单条执行效率高，而且还能有效的降低数据库连接数，提升应用的QPS上限。</span></p>
  <p data-lake-id="u332167bf" id="u332167bf"><br></p>
  <h3 data-lake-id="c621c98d" id="c621c98d"><span data-lake-id="udb89a023" id="udb89a023">使用Future模式</span></h3>
  <p data-lake-id="u5348564c" id="u5348564c"><br></p>
  <p data-lake-id="u5f8b591c" id="u5f8b591c"><span data-lake-id="u7afb9df1" id="u7afb9df1">假设一个任务执行起来需要花费一些时间,为了省去不必要的等待时间,可以先获取一个“提货单”,即Future,然后继续处理别的任务,直到“货物”到达,即任务执行完得到结果,此时便可以用“提货单”进行提货,即通过Future对象得到返回值。</span></p>
  <p data-lake-id="u207402de" id="u207402de"><br></p>
  <pre lang="java"><code>
public class RealData implements Callable&lt;String&gt; {  
    protected String data;  

    public RealData(String data) {  
        this.data = data;  
    }  

    @Override  
    public String call() throws Exception {  
        //利用sleep方法来表示真是业务是非常缓慢的  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        return data;  
    }  
}  

public class Application {  
    public static void main(String[] args) throws Exception {  
        FutureTask&lt;String&gt; futureTask =   
                new FutureTask&lt;String&gt;(new RealData("name"));  
        ExecutorService executor =   
                Executors.newFixedThreadPool(1); //使用线程池  
        //执行FutureTask，相当于上例中的client.request("name")发送请求  
        executor.submit(futureTask);  
        //这里可以用一个sleep代替对其他业务逻辑的处理  
        //在处理这些业务逻辑过程中，RealData也正在创建，从而充分了利用等待时间  
        Thread.sleep(2000);  
        //使用真实数据  
        //如果call()没有执行完成依然会等待  
        System.out.println("数据=" + futureTask.get());  
    }  
}
</code></pre>
  <p data-lake-id="uff352fae" id="uff352fae"><br></p>
  <h3 data-lake-id="5fac9341" id="5fac9341"><span data-lake-id="u550de7cf" id="u550de7cf">使用线程池</span></h3>
  <p data-lake-id="u38ea4193" id="u38ea4193"><br></p>
  <p data-lake-id="uf2a225f2" id="uf2a225f2"><span data-lake-id="ud652c00d" id="ud652c00d">合理利用线程池能够带来三个好处。第一：降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。第二：提高响应速度。当任务到达时，任务可以不需要等到线程创建就能立即执行。第三：提高线程的可管理性。线程是稀缺资源，如果无限制的创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一的分配，调优和监控。</span></p>
  <p data-lake-id="u3ed42bb4" id="u3ed42bb4"><br></p>
  <p data-lake-id="ua1229898" id="ua1229898"><span data-lake-id="u8028802c" id="u8028802c">在 Java 5 之后，并发编程引入了一堆新的启动、调度和管理线程的API。Executor 框架便是 Java 5 中引入的，其内部使用了线程池机制，它在 java.util.cocurrent 包下，通过该框架来控制线程的启动、执行和关闭，可以简化并发编程的操作。</span></p>
  <p data-lake-id="ub8e15814" id="ub8e15814"><br></p>
  <pre lang="java"><code>
public class MultiThreadTest {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
        ExecutorService executor = new ThreadPoolExecutor(2, 5, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue&lt;Runnable&gt;(), threadFactory);
        executor.execute(new Runnable() {
            @Override
            public void run() {
               System.out.println("hello world !");
            }
        });
        System.out.println(" ===&gt; main Thread! " );
    }
}
</code></pre>
  <p data-lake-id="u91cc461a" id="u91cc461a"><br></p>
  <h3 data-lake-id="91a7d4c5" id="91a7d4c5"><span data-lake-id="u2bb4a362" id="u2bb4a362">使用NIO</span></h3>
  <p data-lake-id="u6bc031d1" id="u6bc031d1"><br></p>
  <p data-lake-id="ud32eef5f" id="ud32eef5f"><span data-lake-id="u9151f680" id="u9151f680">JDK自1.4起开始提供全新的I/O编程类库,简称NIO,其不但引入了全新高效的Buffer和Channel,同时,还引入了基于Selector的非阻塞 I/O机制,将多个异步的I/O操作集中到一个或几个线程当中进行处理,使用NIO代替阻塞I/O能提高程序的并发吞吐能力,降低系统的开销。</span></p>
  <p data-lake-id="u5f4d66f8" id="u5f4d66f8"><br></p>
  <p data-lake-id="u5e513813" id="u5e513813"><span data-lake-id="ucb4d4e28" id="ucb4d4e28">对于每一个请求,如果单独开一个线程进行相应的逻辑处理,当客户端的数据传递并不是一直进行,而是断断续续的,则相应的线程需要 I/O等待,并进行上下文切换。而使用NIO引入的Selector机制后,可以提升程序的并发效率,改善这一状况。</span></p>
  <p data-lake-id="u18ea8c74" id="u18ea8c74"><br></p>
  <pre lang="java"><code>
public class NioTest {  
    static public void main( String args[] ) throws Exception {  
        FileInputStream fin = new FileInputStream("c:\\test.txt");  
        // 获取通道  
        FileChannel fc = fin.getChannel();  
        // 创建缓冲区  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
        // 读取数据到缓冲区  
        fc.read(buffer);  
        buffer.flip();  
        while (buffer.remaining()&gt;0) {  
            byte b = buffer.get();  
            System.out.print(((char)b));  
        }  
        fin.close();  
    }  
}
</code></pre>
  <p data-lake-id="ua9911b08" id="ua9911b08"><br></p>
  <h3 data-lake-id="844f4c41" id="844f4c41"><span data-lake-id="u29181d5f" id="u29181d5f">锁优化</span></h3>
  <p data-lake-id="uf9331b82" id="uf9331b82"><br></p>
  <p data-lake-id="u26fbb7ee" id="u26fbb7ee"><span data-lake-id="u8fecb52e" id="u8fecb52e">在并发场景中，我们的代码中经常会用到锁。存在锁，就必然存在锁的竞争，存在锁的竞争，就会消耗很多资源。那么，如何优化我们Java代码中的锁呢？主要可以从以下几个方面考虑：</span></p>
  <p data-lake-id="uf41d8630" id="uf41d8630"><br></p>
  <ul list="ufc411ab1">
   <li fid="u1d4f9f3f" data-lake-id="uf284a29a" id="uf284a29a"><span data-lake-id="u9a9bacc4" id="u9a9bacc4">减少锁持有时间 </span></li>
  </ul>
  <ul list="ufc411ab1" data-lake-indent="1">
   <li fid="u27e5bb9b" data-lake-id="u395fe133" id="u395fe133"><span data-lake-id="u38fc650c" id="u38fc650c">可以使用同步代码块来代替同步方法。这样既可以减少锁持有的时间。</span></li>
  </ul>
  <ul list="ufc411ab1" start="2">
   <li fid="u1d4f9f3f" data-lake-id="ue3efc69e" id="ue3efc69e"><span data-lake-id="u82f4e063" id="u82f4e063">减少锁粒度 </span></li>
  </ul>
  <ul list="ufc411ab1" data-lake-indent="1">
   <li fid="u66669cb4" data-lake-id="u438963cc" id="u438963cc"><span data-lake-id="u2f342c21" id="u2f342c21">要在并发场景中使用Map的时候，记得使用ConcurrentHashMap来代替HashTable和HashMap。</span></li>
  </ul>
  <ul list="ufc411ab1" start="3">
   <li fid="u1d4f9f3f" data-lake-id="uff82956b" id="uff82956b"><span data-lake-id="ua7132351" id="ua7132351">锁分离 </span></li>
  </ul>
  <ul list="ufc411ab1" data-lake-indent="1">
   <li fid="uea4d2823" data-lake-id="u4c05dbcd" id="u4c05dbcd"><span data-lake-id="uc3373ea6" id="uc3373ea6">普通锁（如syncronized）会导致读阻塞写、写也会阻塞读，同时读读与写写之间也会进行阻塞，可以想办法将读操作和写操作分离开。</span></li>
  </ul>
  <ul list="ufc411ab1" start="4">
   <li fid="u1d4f9f3f" data-lake-id="u29f36658" id="u29f36658"><span data-lake-id="u86a9daff" id="u86a9daff">锁粗化 </span></li>
  </ul>
  <ul list="ufc411ab1" data-lake-indent="1">
   <li fid="uad3c5c9a" data-lake-id="u6fa5d3d0" id="u6fa5d3d0"><span data-lake-id="u0fdaaacf" id="u0fdaaacf">有些情况下我们希望把很多次锁的请求合并成一个请求，以降低短时间内大量锁请求、同步、释放带来的性能损耗。</span></li>
  </ul>
  <ul list="ufc411ab1" start="5">
   <li fid="u1d4f9f3f" data-lake-id="u7e4f4aba" id="u7e4f4aba"><span data-lake-id="u84b7ca5a" id="u84b7ca5a">锁消除 </span></li>
  </ul>
  <ul list="ufc411ab1" data-lake-indent="1">
   <li fid="u31acda07" data-lake-id="u839327ff" id="u839327ff"><span data-lake-id="u7b6f8a4f" id="u7b6f8a4f">锁消除是Java虚拟机在JIT编译是，通过对运行上下文的扫描，去除不可能存在共享资源竞争的锁，通过锁消除，可以节省毫无意义的请求锁时间。</span></li>
  </ul>
  <p data-lake-id="uf9ccd693" id="uf9ccd693"><br></p>
  <p data-lake-id="ub65c423d" id="ub65c423d"><span data-lake-id="u300b8371" id="u300b8371">关于锁优化的内容，后面会出一篇文章详细介绍。</span></p>
  <p data-lake-id="u4d15b5f4" id="u4d15b5f4"><br></p>
  <h3 data-lake-id="9ed5fb41" id="9ed5fb41"><span data-lake-id="u2c4d7301" id="u2c4d7301">压缩传输</span></h3>
  <p data-lake-id="ufa000eb6" id="ufa000eb6"><br></p>
  <p data-lake-id="ub6140408" id="ub6140408"><span data-lake-id="u9e4c5df3" id="u9e4c5df3">在进行数据传输之前,可以先将数据进行压缩,以减少网络传输的字节数,提升数据传输的速度,接收端可以将数据进行解压,以还原出传递的数据,并且,经过压缩的数据还可以节约所耗费的存储介质(磁盘或内存)的空间以及网络带宽,降低成本。当然,压缩也并不是没有开销的,数据压缩需要大量的CPU计算,并且,根据压缩算法的不同,计算的复杂度以及数据的压缩比也存在较大差异。一般情况下,需要根据不同的业务场景,选择不同的压缩算法。</span></p>
  <p data-lake-id="uab5f6c93" id="uab5f6c93"><br></p>
  <h3 data-lake-id="c3a7c968" id="c3a7c968"><span data-lake-id="u0485fa05" id="u0485fa05">缓存结果</span></h3>
  <p data-lake-id="ue0efc420" id="ue0efc420"><br></p>
  <p data-lake-id="u817e9c55" id="u817e9c55"><span data-lake-id="u29bc5d83" id="u29bc5d83">对于相同的用户请求,如果每次都重复的查询数据库,重复的进行计算,将浪费很多的时间和资源。将计算后的结果缓存到本地内存,或者是通过分布式缓存来进行结果的缓存,可以节约宝贵的CPU计算资源,减少重复的数据库查询或者是磁盘I/O,将原本磁头的物理转动变成内存的电子运动,提高响应速度,并且线程的迅速释放也使得应用的吞吐能力得到提升。</span></p>
  <p data-lake-id="uafd3bb6d" id="uafd3bb6d"><br></p>
  <h3 data-lake-id="avqLY" id="avqLY"><span data-lake-id="u8cd9045e" id="u8cd9045e">SQL优化</span></h3>
  <p data-lake-id="u41b8bbae" id="u41b8bbae"><br></p>
 </body>
</html>